<?php
/*
 * +----------------------------------------------------------------------
 * | 微信支付引擎
 * +----------------------------------------------------------------------
 * | Copyright (c) 2015 summer All rights reserved.
 * +----------------------------------------------------------------------
 * | Author: summer <aer_c@qq.com> <qq7579476>
 * +----------------------------------------------------------------------
 * | This is not a free software, unauthorized no use and dissemination.
 * +----------------------------------------------------------------------
 * | Date
 * +----------------------------------------------------------------------
 */
 header("Content-type: text/html; charset=utf-8");
class Pay_Engine_Wechat extends Pay_Base {

	 //接口API URL前缀
  const API_URL_PREFIX = 'https://api.mch.weixin.qq.com';
  //下单地址URL
  const UNIFIEDORDER_URL = "/pay/unifiedorder";
  //查询订单URL
  const ORDERQUERY_URL = "/pay/orderquery";
  //关闭订单URL
  const CLOSEORDER_URL = "/pay/closeorder";
 //订单退款 URL
  const REFUND_URL    = "/secapi/pay/refund";

	protected $config = array(
		//公众号的唯一标识
		'appid' => '',

		//商户号
		'mchid' => '',

		//公众号的appsecret
		//'appsecret' => '',

		//微信支付Key
		'key' => '',

		//商户证书
		'cert_path' => 'key/wechat_apiclient_cert.pem',
		'key_path' => 'key/wechat_apiclient_key.pem'
	);

	//网页授权接口微信服务器返回的数据
	protected $data = null;

	//请求参数，类型为关联数组
	protected $param;

	//请求后返回的参数
	protected $values = array();

	public function check() {
        if (!$this->config['appid'] || !$this->config['mchid'] || !$this->config['key']) {
        	DI()->logger->log('payError','wechat setting error');
            return false;
        }
        return true;
    }

    /**
     * 请求支付
     * @param  [type] $data [description]
     * @return [type]       [description]
     */
    public function buildRequestForm($params) {
    	$this->body = $params['body'];
	    $this->out_trade_no = $params['order_no'];
	    $this->total_fee = $params['price']*100;
	    $this->trade_type = 'APP';
	    $this->nonce_str = $this->createNoncestr(32);
	    $this->spbill_create_ip = $_SERVER['REMOTE_ADDR'];
	    //$this->attach=$params['out_trade_no'];

	    $this->params['appid'] = $this->config['appid'];
	    $this->params['mch_id'] =$this->config['mchid'];
	    $this->params['nonce_str'] = $this->nonce_str;
	    $this->params['body'] = $this->body;
	    $this->params['out_trade_no'] = $this->out_trade_no;
	    $this->params['total_fee'] = $this->total_fee;
	    $this->params['spbill_create_ip'] = $this->spbill_create_ip;
	    $this->params['trade_type'] = $this->trade_type;
	    $this->params['notify_url'] = $this->config['notify_url'];
	   // $this->params['attach']     =  $this->attach;
	    //获取签名数据
	    $this->sign = $this->getSign($this->params);
	    //print_r($this->sign);
	    $this->params['sign'] = $this->sign;
	    //print_r($this->params);
	    $xml = $this->arrayToXml($this->params);
	    //print_r($xml);
	    $response = $this->postXmlCurl($xml, self::API_URL_PREFIX.self::UNIFIEDORDER_URL);
	    //print_r($response);
	    if( !$response ){
	      return false;
	    }
	    $result = $this->xml_to_data( $response );
	    if( !empty($result['result_code']) && !empty($result['err_code']) || $result['result_code']=='FAIL' ){
	      $result['err_msg'] = $this->error_code( $result['err_code'] );   
	       return $result;
	    }else{
        $data= $this->getAppPayParams($result['prepay_id']);
	      $data['out_trade_no']=$this->out_trade_no;
       return   $data;
	   }
    }

   
  /**
    * 生成APP端支付参数
    * @param  $prepayid 预支付id
    */
   public function getAppPayParams( $prepayid ){
     $data['appid']     = $this->config['appid'];
     $data['partnerid'] =$this->config['mchid'];
     $data['prepayid']  =$prepayid;
     $data['package']   ='Sign=WXPay';
     $data['noncestr']  =$this->createNoncestr(32);
     $data['timestamp'] =time();
     $data['sign']      =$this->getSign( $data ); 
     return $data;
   }


    /**
   * 查询订单信息
   * @param $out_trade_no   订单号
   * @return array
   */
  public function orderQuery($data){
    
    $this->params['appid'] = $this->config['appid'];
    $this->params['mch_id'] = $this->config['mchid'];
    $this->params['nonce_str'] = $this->createNoncestr(32);
    $this->params['out_trade_no'] =$data['out_trade_no'];
    //$this->params['transaction_id'] = $out_trade_no;
    //获取签名数据
    $this->sign = $this->getSign( $this->params );
    $this->params['sign'] = $this->sign;
    $xml = $this->arrayToXml($this->params);
    $response = $this->postXmlCurl($xml, self::API_URL_PREFIX.self::ORDERQUERY_URL);
    if( !$response ){
      return false;
    }
    $result = $this->xml_to_data( $response );
    if( !empty($result['result_code']) && !empty($result['err_code']) ){
      $result['err_msg'] = $this->error_code( $result['err_code'] );
    }else{
      //return $result; 
      return true;
    } 
    
   }
  
  /**
   * 关闭订单
   * @param $out_trade_no   订单号
   * @return array
   */
  public function closeOrder( $data ){
    $this->params['appid'] = $this->config['appid'];
    $this->params['mch_id'] = $this->config['mchid'];
    $this->params['nonce_str'] = $this->createNoncestr(32);
    $this->params['out_trade_no'] = $data['out_trade_no'];
    
    //获取签名数据
    $this->sign = $this->getSign( $this->params);
    $this->params['sign'] = $this->sign;
    $xml = $this->arrayToXml($this->params);
    $response = $this->postXmlCurl($xml, self::API_URL_PREFIX.self::CLOSEORDER_URL);
    if( !$response ){
      return false;
    }
    $result = $this->xml_to_data( $response );
   if( !empty($result['result_code']) && !empty($result['err_code']) ){
      $result['err_msg'] = $this->error_code( $result['err_code'] );
         
    }else{
      //return $result; 
      return true;
    } 
  }
  
  /**
  *申请退款
  */
  public function refund($data){
    $this->params['appid'] = $this->config['appid'];
    $this->params['mch_id'] = $this->config['mchid'];
    $this->params['nonce_str'] = $this->createNoncestr(32);  
    $this->params['transaction_id'] = $data['trade_no'];
    $this->params['out_refund_no']=$data['order_id'];
    $this->params['total_fee']=intval($data['total_fee']*100);
    $this->params['refund_fee']=$data['money']*100;
    $this->params['op_user_id']=$this->config['mchid'];
   
    //获取签名数据
    $this->sign = $this->getSign( $this->params);
    $this->params['sign'] = $this->sign;
    $xml = $this->arrayToXml($this->params);
    $response = $this->postXmlCurl($xml, self::API_URL_PREFIX.self::REFUND_URL,true);
    if( !$response ){
      return false;
    }
    $result = $this->xml_to_data( $response );
   if( !empty($result['result_code']) && !empty($result['err_code']) ){
        $result['err_msg'] = $this->error_code( $result['err_code'] );
         return $result;
    }else{
      return $result; 
      //return true;
    }  
  }

  /**
   * 
   * 获取支付结果通知数据
   * return array
   */
  public function getNotifyData(){
    //获取通知的数据
    $xml = $GLOBALS['HTTP_RAW_POST_DATA'];
    $data = array();
    if( empty($xml) ){
      return false;
    }
    $data = $this->xml_to_data( $xml );
    if( !empty($data['return_code']) ){
      if( $data['return_code'] == 'FAIL' ){
        return false;
      }
    }
    return $data;
  }
  
  /**
   * 接收通知成功后应答输出XML数据
   * @param string $xml
   */
  public function notifySuccess(){
    $data['return_code'] = 'SUCCESS';
    $data['return_msg'] = 'OK';
    $xml = $this->arrayToXml( $data );
    echo $xml;
    die();
  }
  /**
     * 请求验证
     */
    public function verifyNotify($notify) {
    	//xml转array
    	$this->values = $this->xml_to_data($notify);
		if($this->values['return_code'] != 'SUCCESS'){
			DI()->logger->log('payError','支付失败', $this->values);
			return false;
		}

		if(!$this->checkSign()){
			DI()->logger->log('payError','签名错误', $this->values);
			return false;
		}

		//写入订单信息
		$this->setInfo($this->values);
		return true;
    }
   
    /**
     * 写入订单信息
     * @param [type] $notify [description]
     */
    protected function setInfo($notify) {
        $info = array();
        //支付状态
        $info['status'] = ($notify['return_code'] == 'SUCCESS') ? 1 : 0;
        $info['money'] = $notify['total_fee']/100;
        //商户订单号
        $info['order_id'] = $notify['out_trade_no'];
        //微信交易号
        $info['trade_no'] = $notify['transaction_id'];
        //交易类型
        $info['type']   ="Wechat";
        $this->info = $info;
    }

    
    /**
	 * 
	 * 检测签名
	 */
	protected function checkSign(){
		if(!array_key_exists('sign', $this->values)){
			return false;
		}
		
		$sign = $this->getSign($this->values);
		if($this->values['sign'] == $sign){
			return true;
		}
		return false;
	}

	/**
	 * 
	 * 构造获取code的url连接
	 * @param string $redirectUrl 微信服务器回跳的url，需要url编码
	 * 
	 * @return 返回构造好的url
	 */
	private function __createOauthUrlForCode($redirectUrl){
		$urlObj["appid"] = $this->config['appid'];
		$urlObj["redirect_uri"] = "$redirectUrl";
		$urlObj["response_type"] = "code";
		$urlObj["scope"] = "snsapi_base";
		$urlObj["state"] = "STATE"."#wechat_redirect";
		$bizString = $this->toUrlParams($urlObj);
		return $this->snsapi_base_url.$bizString;
	}

	/**
	 * 
	 * 构造获取open和access_toke的url地址
	 * @param string $code，微信跳转带回的code
	 * 
	 * @return 请求的url
	 */
	private function __createOauthUrlForOpenid($code){
		$urlObj["appid"] = $this->config['appid'];
		$urlObj["secret"] = $this->config['appsecret'];
		$urlObj["code"] = $code;
		$urlObj["grant_type"] = "authorization_code";
		$bizString = $this->toUrlParams($urlObj);
		return $this->openid_url.$bizString;
	}

	/**
	 * 
	 * 拼接签名字符串
	 * @param array $urlObj
	 * 
	 * @return 返回已经拼接好的字符串
	 */
	private function toUrlParams($urlObj){
		$buff = "";
		foreach ($urlObj as $k => $v)
		{
			if($k != "sign" && $v !== ''){
				$buff .= $k . "=" . $v . "&";
			}
		}
		
		$buff = trim($buff, "&");
		return $buff;
	}

	/**
     *  产生随机字符串，不长于32位
     */
    private function createNoncestr( $length = 32 ){
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";  
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {  
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);  
        }  
        return $str;
    }

    /**
     *  生成签名
     */
    private function getSign($data){
        //第一步：对参数按照key=value的格式，并按照参数名ASCII字典序排序如下：
        ksort($data);
        $string = $this->toUrlParams($data);

        //第二步：拼接API密钥
        $string = $string."&key=".$this->config['key'];

        //MD5加密
        $string = md5($string);

        //将得到的字符串全部大写并返回
        return strtoupper($string);
    }

    /**
     * 	array转xml
     */
    private function arrayToXml($arr){
        $xml = "<xml>";
        foreach ($arr as $key=>$val)
        {
            if (is_numeric($val))
            {
                $xml=$xml."<".$key.">".$val."</".$key.">";

            }
            else
                $xml=$xml."<".$key."><![CDATA[".$val."]]></".$key.">";
        }
        $xml=$xml."</xml>";
        return $xml;
    }

   /**
     * 将xml转为array
     * @param string $xml
     * return array
     */
  public function xml_to_data($xml){  
    if(!$xml){
      return false;
	    }
	        //将XML转为array
	        //禁止引用外部xml实体
	        libxml_disable_entity_loader(true);
	        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);    
	    return $data;
	  }
    

    /**
   * 以post方式提交xml到对应的接口url
   * 
   * @param string $xml  需要post的xml数据
   * @param string $url  url
   * @param bool $useCert 是否需要证书，默认不需要
   * @param int $second   url执行超时时间，默认30s
   * @throws WxPayException
   */
  private function postXmlCurl($xml, $url, $useCert = false, $second = 30){   
    $ch = curl_init();
    //设置超时
    curl_setopt($ch, CURLOPT_TIMEOUT, $second);
    
    curl_setopt($ch,CURLOPT_URL, $url);
    curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
    curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);
    //设置header
    curl_setopt($ch, CURLOPT_HEADER, FALSE);
    //要求结果为字符串且输出到屏幕上
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  
    if($useCert == true){
      //设置证书
      //使用证书：cert 与 key 分别属于两个.pem文件
      curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
      curl_setopt($ch,CURLOPT_SSLCERT,dirname(dirname(__FILE__)). '/' . $this->config['cert_path'] );
      curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
      curl_setopt($ch,CURLOPT_SSLKEY,dirname(dirname(__FILE__)). '/' .$this->config['key_path'] );
    }
    //post提交方式
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
    //运行curl
    $data = curl_exec($ch);
    //返回结果
    if($data){
      curl_close($ch);
      return $data;
    } else { 
      $error = curl_errno($ch);
      curl_close($ch);
      return false;
    }
   }
   /**
    * 错误代码
    * @param  $code   服务器输出的错误代码
    * return string
    */
   public function error_code( $code ){
     $errList = array(
      'NOAUTH'        =>  '商户未开通此接口权限',
      'NOTENOUGH'       =>  '用户帐号余额不足',
      'ORDERNOTEXIST'     =>  '订单号不存在',
      'ORDERPAID'       =>  '商户订单已支付，无需重复操作',
      'ORDERCLOSED'     =>  '当前订单已关闭，无法支付',
      'SYSTEMERROR'     =>  '系统错误!系统超时',
      'APPID_NOT_EXIST'   =>  '参数中缺少APPID',
      'MCHID_NOT_EXIST'   =>  '参数中缺少MCHID',
      'APPID_MCHID_NOT_MATCH' =>  'appid和mch_id不匹配',
      'LACK_PARAMS'     =>  '缺少必要的请求参数',
      'OUT_TRADE_NO_USED'   =>  '同一笔交易不能多次提交',
      'SIGNERROR'       =>  '参数签名结果不正确',
      'XML_FORMAT_ERROR'    =>  'XML格式错误',
      'REQUIRE_POST_METHOD' =>  '未使用post传递参数 ',
      'POST_DATA_EMPTY'   =>  'post数据不能为空',
      'NOT_UTF8'        =>  '未使用指定编码格式',
      'REFUND_FEE_MISMATCH'=>'订单金额或退款金额不一致',
      'REFUND_FEE_INVALID'=>'累计退款金额大于支付金额',
     ); 
     if( array_key_exists( $code , $errList ) ){
      return $errList[$code];
     }
   }  
	
}
